package edu.pku.sei.ocl.utilities;

import java.lang.reflect.Method;

import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.UniqueEList;

import edu.pku.sei.ocl.evaluation.Environment;

public class StandardLibraryUtil {
	static public EList<Class<?>> staticLib = new UniqueEList<Class<?>>(10);
	
	static {
		staticLib.add(OCLStandardLibrary.class);
		staticLib.add(EnvironmentHelperLibrary.class);
	}
	
	static private CacheUtil  cache = new CacheUtil();
	
	static public Object invokeHelperMethod(String name, Environment env, Object[] params) throws Exception{
		Class<?> selfCls = env.getClass();
		Class<?> paramsCls[] = new  Class<?>[params.length+1];
		paramsCls[0] = selfCls;
		for(int i=0;i<params.length;i++){
			paramsCls[i+1] = params[i].getClass();
		}
		
		Object result = OCLConstant.OCLInvalid;
		Method m = null;

		m = searchStlMethod(name,paramsCls);
		
		if(m!=null) {
			
			Class<?>[] types = m.getParameterTypes();
			
			Object[] actualParams = new Object[params.length+1];
			
			actualParams[0] = types[0].cast(env);
			
			for(int i=0;i<params.length;i++){
				actualParams[i+1] = types[i+1].cast(params[i]);
			}
			
			try {
				result = m.invoke(null, actualParams);
			} catch (Exception e) {
				System.out.println("invoke stl method error!");
			}
		} else throw new Exception("no such helper ("+name+") is found!");
		
		return result;
	}
	
	private static Method searchStaticMethod(String name, Class<?>[] paramsCls,
			EList<Class<?>> lib) {
		Method m = null;
		
		m = searchCachedMethod(name, paramsCls, true);
		
		if(m!=null)
			return m;
		
		for(Class<?> cls : lib){
			m = searchMethod(cls,name,paramsCls,true);
			if(m==null) continue;
			return m;
		}
		
		return null;
	}

	static public Object invokeMethod(String name,Object self,Object[] params){
		Class<?> selfCls = self.getClass();
		Class<?> paramsCls[] = new  Class<?>[params.length+1];
		paramsCls[0] = selfCls;
		for(int i=0;i<params.length;i++){
			paramsCls[i+1] = params[i].getClass();
		}
		
		Object result = OCLConstant.OCLInvalid;
		Method m = null;

		m = searchStlMethod(name,paramsCls);
		if(m!=null){
			Class<?>[] types = m.getParameterTypes();
			
			Object[] actualParams = new Object[params.length+1];
			
			actualParams[0] = types[0].cast(self);
			
			for(int i=0;i<params.length;i++){
				actualParams[i+1] = types[i+1].cast(params[i]);
			}
			
			try {
				result = m.invoke(null, actualParams);
			} catch (Exception e) {
				System.out.println("invoke stl method error!");
			}
		} else 	{
			m = searchObjMethod(name, paramsCls);
			if(m!=null){
				Class<?>[] types = m.getParameterTypes();
				
				Object[] actualParams = new Object[params.length];
				
				for(int i=0;i<params.length;i++){
					if(types[i].isPrimitive()==false)
						actualParams[i] = types[i].cast(params[i]);
					else actualParams[i] = params[i];
				}
				
				try {
					result = m.invoke(self, actualParams);
				} catch (Exception e) {
					System.out.println("invoke obj method error!");
				}
			}
		}
		return result;
	}
	
	static public Object invokeStlMethod(String name,Object self,Object[] params) {
		Class<?> selfCls = self.getClass();
		Class<?> paramsCls[] = new  Class<?>[params.length+1];
		paramsCls[0] = selfCls;
		for(int i=0;i<params.length;i++){
			paramsCls[i+1] = params[i].getClass();
		}
		
		return invokeStlMethod(name,self,params,paramsCls);
	}
	
	static public Object invokeObjMethod(String name,Object self,Object[] params){
		Class<?> selfCls = self.getClass();
		Class<?> paramsCls[] = new  Class<?>[params.length+1];
		paramsCls[0] = selfCls;
		for(int i=0;i<params.length;i++){
			paramsCls[i+1] = params[i].getClass();
		}
		return invokeObjMethod(name,self,params,paramsCls);
	}
	
	static public Object invokeObjMethod(String name,Object self,Object[] params,Class<?>[] paramsCls){
		Method m = searchObjMethod(name, paramsCls);
		
		Object result = OCLConstant.OCLInvalid;
		
		if(m!=null){
			Class<?>[] types = m.getParameterTypes();
			
			Object[] actualParams = new Object[params.length];
			
			for(int i=0;i<params.length;i++){
				if(types[i].isPrimitive()==false)
					actualParams[i] = types[i].cast(params[i]);
				else actualParams[i] = params[i];
			}
			
			try {
				result = m.invoke(self, actualParams);
			} catch (Exception e) {
				System.out.println("invoke obj method error!");
			}
		}
		
		return result;
	}
	
	static public Object invokeStlMethod(String name,Object self,Object[] params,Class<?>[] paramsCls) {
		Method m = searchStlMethod(name,paramsCls);
		
		Object result = OCLConstant.OCLInvalid;
		
		if(m!=null){
			Class<?>[] types = m.getParameterTypes();
			
			Object[] actualParams = new Object[params.length+1];
			
			actualParams[0] = types[0].cast(self);
			
			for(int i=0;i<params.length;i++){
				actualParams[i+1] = types[i+1].cast(params[i]);
			}
			
			try {
				result = m.invoke(null, actualParams);
			} catch (Exception e) {
				System.out.println("invoke stl method error!");
			}
		}
		
		return result;
	}
	
	static public Method searchStlMethod(String name,Class<?>[] paramsCls){
		return searchStaticMethod(name,paramsCls,staticLib);
	}
	
	static public Method searchObjMethod(String name, Class<?>[] paramsCls){
		Method m = null;
		
		m = searchCachedMethod(name, paramsCls, false);
		
		if(m==null)
			m = searchMethod(paramsCls[0],name,paramsCls,false);
		
		return m;
	}
	
	static private Method searchMethod(Class<?> cls, String name, Class<?>[] paramsCls,boolean stl){
		
		Method m = null;
		Method[] methods = cls.getMethods();
		
		if(!stl&&cls!=paramsCls[0])
			return null;
		
		int size = stl ? paramsCls.length : paramsCls.length-1;
		
		for(Method meth : methods){
			if(name.equals(meth.getName())==false) continue;
			
			Class<?>[] mpts = meth.getParameterTypes();
			
			if(mpts.length==size){
				
				if(checkParameterType(paramsCls, mpts,stl)){
					m = meth;
					break;
				}
				
			}
		}

		if(m!=null){
			cache.add(m, name);
		}
		
		return m;
	}
	
	static public Method searchCachedMethod(String name, Class<?>[] paramsCls,boolean stl){
		EList<Object> list = (EList<Object>)cache.get(name);
		
		if(list == null) return null;
		
		int size = stl ? paramsCls.length : paramsCls.length-1;
		
		for(Object mo : list){
			
			Method m = (Method)mo;
			
			if(!stl&&m.getDeclaringClass()!=paramsCls[0])
				continue;
			
			Class<?>[] mpts = m.getParameterTypes();
			
			if(mpts.length==size){
				
				if(checkParameterType(paramsCls, mpts,stl)){
					return m;
				}
				
			}
		}
		
		return null;
	}

	private static boolean checkParameterType(Class<?>[] paramsCls,
			Class<?>[] mpts, boolean stl) {
		
		int j = stl ? 0 : 1;
		
		for(int i=0;i<mpts.length;i++,j++){
			if(isAssignableFrom(paramsCls[j],mpts[i])==false) {
				return false;
			}
		}
		
		return true;
	}
	
	static private boolean isAssignableFrom(Class<?> ca,Class<?> cb){
		if(cb.isAssignableFrom(ca))
			return true;
		if(checkClasses(ca,int.class,Integer.class,float.class,Float.class,double.class,Double.class)&&
				checkClasses(cb,int.class,Integer.class,float.class,Float.class,double.class,Double.class))
			return true;
		if(checkClasses(ca,boolean.class,Boolean.class)&&checkClasses(cb,boolean.class,Boolean.class))
			return true;
		return false;
	}
	
	static private boolean checkClasses(Class<?> type, Class<?>... types){
		for(Class<?> t : types)
			if(type==t) return true;
		return false;
	}
	
	static public void main(String[] argv){
		staticLib.add(OCLStandardLibrary.class);
		String str = "xtx";
		
		
		System.out.println(invokeMethod("at",str,new Object[]{0}));
		System.out.println(invokeMethod("charAt",str,new Object[]{1}));
		
		//System.out.println(double.class.cast((Object)1));
	}

}
